package cn.ycc1.functionlibrary.reflection;

/**
 * Working with Records
 * @author ycc
 * @date 2025/3/9
 * Records are a particular type of class, that strongly enforce non-modifiability. It also introduces the notion of record
 * component. Both elements have an impact on the Reflection API.
 */
public class WorkingWithRecords {
    /**
     * Identifying Record Type
     * When were introduced in Java SE 16, several new methods and classes were added to the Reflection API. Among them is a
     * method on the class Class to check if a given class is a record class : Class.isRecord(). You can learn more about
     * records on this page.
     *
     * Suppose that you have the following record.
     *
     * public record Point(int x, int y) {
     *     public Point() {
     *         this(0, 0);
     *     }
     * }
     * You can see this simple method in action on the following example.
     *
     * Class<?> c1 = String.class;
     * Class<?> c2 = Point.class;
     * System.out.println("Is " + c1.getSimpleName() + " a record class? " + c1.isRecord());
     * System.out.println("Is " + c2.getSimpleName() + " a record class?  " + c2.isRecord());
     * Running the previous code prints the following.
     *
     * Is String a record class? false
     * Is Point a record class?  true
     */

    /**
     * Getting Components Information
     * Records also define a new element for the Java language, which is the record component. A record component is one of the
     * elements declared along with the declaration of a record. The Reflection API also supports this notion of record component.
     *
     * The Reflection API gets a new class: RecordComponent that models a component of a record. You have several methods on
     * this class.
     *
     * Component API	Comments
     * getDeclaringRecord()	Returns the class that declares this component
     * getAccessor()	Returns the method that models the accessor of this component
     * getName()	Returns the name of this component.
     * getType()	Returns the type of this component, as a Class object.
     * getGenericType()	Returns the generic type of this component, as a Type object.
     * Then you can get the components of a record in an array of type RecordComponent[].
     *
     * Let us see these methods in action.
     *
     * Class<?> c = Point.class;
     *
     * RecordComponent[] components = c.getRecordComponents();
     * Class<?> declaring = components[0].getDeclaringRecord();
     * System.out.println("Declaring record: " + declaring);
     *
     * String name = components[0].getName();
     * System.out.println("name = " + name);
     *
     * Method accessor = components[0].getAccessor();
     * System.out.println("accessor = " + accessor);
     *
     * Class<?> type = components[0].getType();
     * System.out.println("type = " + type);
     *
     * Type genericType = components[0].getGenericType();
     * System.out.println("genericType = " + genericType);
     *
     * String genericSignature = components[0].getGenericSignature();
     * System.out.println("genericSignature = " + genericSignature);
     * Running the previous code prints the following.
     *
     * Declaring record: class org.devjava.Point
     * name = x
     * accessor = public int org.devjava.Point.x()
     * type = int
     * genericType = int
     * genericSignature = null
     */

    /**
     * Accessing Record Fields
     * Note that there is no method on the RecordComponent class to get a reference on the Field object that models the field
     * of this component.
     *
     * You can still get a reference on the Field of records using the Class.getDeclaredFields() method. But you cannot modify
     * the field of a record, even by calling Field.setAccessible(true). This behavior is specific to records.
     *
     * Let us examine the following example. Suppose that you have the following record.
     *
     * public record Point(int x, int y) {}
     * This record has two final instance fields x and y, that you can get using the Reflection API.
     *
     * Class<?> c = Point.class;
     * Field[] fields = c.getDeclaredFields();
     * for (Field field : fields) {
     *     System.out.println("field = " + field);
     * }
     * Running the previous code prints the following.
     *
     * field = private final int org.devjava.Point.x
     * field = private final int org.devjava.Point.y
     * You can use these fields to read their values, as you can see on the following example.
     *
     * Point p = new Point(1, 2);
     * Field xField = p.getClass().getDeclaredField("x");
     * xField.setAccessible(true);
     * int x = (int)xField.get(p);
     * System.out.println("x = " + x);
     * Note that the x field is private, reason why you need to call xField.setAccessible(true).
     *
     * The previous example prints the following.
     *
     * x = 1
     * Note that you could have also used xField.getInt(p) to get the value of x, to avoid auto-unboxing.
     *
     * But you cannot use this modify the value of x. You can try to run the following code.
     *
     * Point p = new Point(1, 2);
     * Field xField = p.getClass().getDeclaredField("x");
     * xField.setAccessible(true);
     * xField.setInt(p, 0);
     * System.out.println("p = " + p);
     * You will then get the following exception IllegalAccessException.
     *
     * Exception in thread "main" java.lang.IllegalAccessException: Can not set final int field org.devjava.Point.x to (int)0
     *     at java.base/jdk.internal.reflect.FieldAccessorImpl.throwFinalFieldIllegalAccessException(FieldAccessorImpl.java:137)
     *     at java.base/jdk.internal.reflect.FieldAccessorImpl.throwFinalFieldIllegalAccessException(FieldAccessorImpl.java:161)
     *     at java.base/jdk.internal.reflect.MethodHandleIntegerFieldAccessorImpl.setInt(MethodHandleIntegerFieldAccessorImpl.java:160)
     *     at java.base/java.lang.reflect.Field.setInt(Field.java:1031)
     *     at org.devjava.Main.main(Main.java:25)
     */
}
